package com.xxl.job.core.rpc.netcom;

import com.xxl.job.core.rpc.codec.RpcRequest;
import com.xxl.job.core.rpc.codec.RpcResponse;
import com.xxl.job.core.rpc.serialize.HessianSerializer;
import com.xxl.job.core.util.HttpClientUtil;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * rpc proxy
 * @author xuxueli 2015-10-29 20:18:32
 */
@Slf4j
public class NetComClientProxy implements FactoryBean<Object> {
	
	// ---------------------- config ----------------------
	private Class<?> iface;
	private String serverAddress;
	private String accessToken;
	
	
	public NetComClientProxy(Class<?> iface, String serverAddress, String accessToken) {
		this.iface = iface;
		this.serverAddress = serverAddress;
		this.accessToken = accessToken;
	}

	@Override
	public Object getObject() throws Exception {
		return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] {iface},new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

						// filter method like "Object.toString()"
						if (Object.class.getName().equals(method.getDeclaringClass().getName())) {
							log.error(">>>>>>>>>>> xxl-rpc proxy class-method not support [{}.{}]", method.getDeclaringClass().getName(), method.getName());
							throw new RuntimeException("xxl-rpc proxy class-method not support");
						}
						
						// request
						RpcRequest request = new RpcRequest();
	                    request.setServerAddress(serverAddress);
	                    request.setCreateMillisTime(System.currentTimeMillis());
	                    request.setAccessToken(accessToken);
	                    request.setClassName(method.getDeclaringClass().getName());
	                    request.setMethodName(method.getName());
	                    request.setParameterTypes(method.getParameterTypes());
	                    request.setParameters(args);

	                    log.info("发起网络请求serverAddress :{} ,className: {}, methodName : {},",serverAddress,request.getClassName(),request.getMethodName());
	                    // send
	                    RpcResponse response = send(request);
	                    
	                    log.info("发起网络请求处理完成,serverAddress :{} ,request: {}, response : {}, interface : {}",serverAddress,request,response,iface.getName());
	                    
	                    // valid response
						if (response == null) {
							log.error(">>>>>>>>>>> xxl-rpc response not found.");
							throw new Exception(">>>>>>>>>>> xxl-rpc response not found.");
						}
	                    if (response.isError()) {
	                        throw new RuntimeException(response.getError());
	                    }
	                    else {
	                        return response.getResult();
	                    }
					}
				});
	}
	@Override
	public Class<?> getObjectType() {
		return iface;
	}
	@Override
	public boolean isSingleton() {
		return false;
	}
	
	/**
	 * 网络通信
	 * @param request
	 * @return
	 * @throws Exception
	 */
	private final RpcResponse send(RpcRequest request) throws Exception {
		try {
			// serialize request
			byte[] requestBytes = HessianSerializer.serialize(request);

			// reqURL
			// http://127.0.0.1:8080/xxl-job-admin/api
			String requestUrl = request.getServerAddress();
			if (requestUrl!=null && requestUrl.toLowerCase().indexOf("http")==-1) {
				requestUrl = "http://" + request.getServerAddress() + "/";	// IP:PORT, need parse to url
			}

			// remote invoke
			byte[] responseBytes = HttpClientUtil.postRequest(requestUrl,requestBytes);
			if (responseBytes == null || responseBytes.length==0) {
				RpcResponse rpcResponse = new RpcResponse();
				rpcResponse.setError("RpcResponse byte[] is null");
				return rpcResponse;
            }

            // deserialize response
			RpcResponse rpcResponse = (RpcResponse) HessianSerializer.deserialize(responseBytes);
			return rpcResponse;
		}
		catch (Exception e) {
			log.error(e.getMessage(), e);

			RpcResponse rpcResponse = new RpcResponse();
			rpcResponse.setError("Client-error:" + e.getMessage());
			return rpcResponse;
		}
	}
}